package com.example.baidu.speech.realtime.websocket;

import com.alibaba.fastjson2.JSON;
import com.example.baidu.speech.realtime.components.SpringContext;
import com.example.baidu.speech.realtime.dto.BaiduSpeechRealtimeDTO;
import com.example.baidu.speech.realtime.service.BaiduSpeechRealtimeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ServerEndpoint 将目前的类定义成一个WebSocket服务器端
 * 想知道 @OnOpen、@OnClose、@OnMessage、@OnError 这些注解方法有哪些自带参数，可以参考方法【org.apache.tomcat.websocket.pojo.PojoMethodMapping#getPathParams】
 */
@Slf4j
@Component
@ServerEndpoint("/ws/baidu/speech/realtime/{webSocketId}")
public class BaiduSpeechRealtimeWebSocket {

    /**
     * 缓存已连接的客户端
     */
    public static Map<String, Session> CONNECTED_CLIENTS = new ConcurrentHashMap<>();

    /**
     * 建立 WebSocket 连接后的唯一标识
     */
    private String webSocketId;

    @OnOpen // WebSocket连接成功后触发
    public void onOpen(Session session, EndpointConfig endpointConfig, @PathParam("webSocketId") String webSocketId) {
        // session.getId() 不能知道具体哪个客户端调进来的，可通过传入的方式来区分它们。例如用户ID
        this.webSocketId = webSocketId;
        // 缓存当前客户端
        cacheWebSockets(session);

        log.info("客户端[{}]与服务端已建立 WebSocket 连接，可开始发送消息。", this.webSocketId);
        // log.info("endpointConfig = {}", JSON.toJSONString(endpointConfig));

    }

    /**
     * 缓存新来的客户端连接
     *
     * @param session session
     */
    private void cacheWebSockets(Session session) {
        if (CONNECTED_CLIENTS.containsKey(this.webSocketId)) {
            try {
                Session oldSession = CONNECTED_CLIENTS.get(this.webSocketId);
                oldSession.close();
                log.info("检测到客户端[{}]有新的Session，已将旧的Session关闭", this.webSocketId);
            } catch (IOException e) {
                log.error("检测到客户端[{}]有新的Session，旧的Session关闭失败", this.webSocketId);
                log.error(e.getMessage(), e);
                throw new RuntimeException(e);
            }
        }
        CONNECTED_CLIENTS.put(this.webSocketId, session);
    }

//    /**
//     * 接收到客户端消息后触发 JSON
//     *
//     * @param session session-可以跟踪和识别特定的用户或客户端连接
//     * @param message 从客户端接收到的消息
//     */
//    @OnMessage
//    public void onMessage(Session session, String message) {
//        log.info("服务端接收到客户端[{}]发来的消息内容 ==> [message = {}]", this.webSocketId, message);
//
//        message = message.concat("  --我是服务端，我已经接受到你发送的消息了");
//        replyMessage(this.webSocketId, message);
//
//        BaiduSpeechRealtimeDTO baiduSpeechRealtimeDTO = new BaiduSpeechRealtimeDTO();
//        baiduSpeechRealtimeDTO.setWebSocketId(this.webSocketId);
//        SpringContext.getBean(BaiduSpeechRealtimeService.class).recognizingRealtimeSpeech(baiduSpeechRealtimeDTO);
//    }

    /**
     * 接收到客户端消息后触发 byte[]
     *
     * @param session session-可以跟踪和识别特定的用户或客户端连接
     * @param bytes   从客户端接收到的二进制消息
     */
    @OnMessage
    public void onMessage(Session session, byte[] bytes) {
        log.info("服务端接收到客户端[{}]发来的消息内容 ==> [bytes.length = {}]", this.webSocketId, bytes.length);

//        message = message.concat("  --我是服务端，我已经接受到你发送的消息了");
//        replyMessage(this.webSocketId, message);

        BaiduSpeechRealtimeDTO baiduSpeechRealtimeDTO = new BaiduSpeechRealtimeDTO();
        baiduSpeechRealtimeDTO.setWebSocketId(this.webSocketId);
        baiduSpeechRealtimeDTO.setInputStream(new ByteArrayInputStream(bytes));
        SpringContext.getBean(BaiduSpeechRealtimeService.class).recognizingRealtimeSpeech(baiduSpeechRealtimeDTO);
    }

    /**
     * 发生错误时的回调
     */
    @OnError
    public void onError(Throwable throwable) {
        log.error("WebSocket发生异常[ERROR]", throwable);
        throw new RuntimeException("WebSocket发生异常");
    }

    @OnClose // WebSocket连接断开后触发
    public void onClose(Session session, CloseReason closeReason) {
        log.info("客户端[{}]已经断开连接，断开原因为[closeReason = {}]", this.webSocketId, JSON.toJSONString(closeReason));
        Session toBeRemovedSession = CONNECTED_CLIENTS.get(this.webSocketId);
        CONNECTED_CLIENTS.remove(this.webSocketId, toBeRemovedSession);

        // 显示所有在线客户端WebSocketId
        displayOnlineClients();
    }

    /**
     * 回复消息
     *
     * @param webSocketId 客户端唯一标识
     * @param message     服务端回复给客户端的消息
     */
    public static void replyMessage(String webSocketId, String message) {
        if (!CONNECTED_CLIENTS.containsKey(webSocketId)) {
            throw new RuntimeException("客户端[" + webSocketId + "]不存在，停止发送消息");
        }
        Session session = CONNECTED_CLIENTS.get(webSocketId);
        if (!session.isOpen()) {
            throw new RuntimeException("客户端 [" + webSocketId + "]已断开连接，停止发送消息");
        }

        try {
            log.info("服务端回复给客户端[{}]的消息内容 ==> [message = {}]", webSocketId, message);
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            throw new RuntimeException("服务端回复客户端[" + webSocketId + "]失败");
        }
    }

    /**
     * 给所有 WebSocket 客户端发送消息
     *
     * @param message 消息
     */
    public static void sendMessageToAllClients(String message) {
        for (String webSocketId : CONNECTED_CLIENTS.keySet()) {
            try {
                replyMessage(webSocketId, message);
            } catch (Exception e) {
                log.error("[广播]给客户端[{}]发送消息失败", webSocketId);
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 显示所有连接中的客户端的webSocketId
     */
    public static void displayOnlineClients() {
        log.info("========== 连接中的客户端 开始 ==========");
        if (!CONNECTED_CLIENTS.isEmpty()) {
            for (String webSocketId : CONNECTED_CLIENTS.keySet()) {
                log.info("++ 客户端[{}]", webSocketId);
            }
        } else {
            log.info("++ 所有客户端均已断开连接");
        }
        log.info("========== 连接中的客户端 结束 ==========");
    }

}
